package com.aceitunaproject.server.player;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.aceitunaproject.server.AceitunaContext;
import com.aceitunaproject.server.ServerConfiguration;
import com.aceitunaproject.server.entity.unit.Unit;
import com.aceitunaproject.server.installation.GoodSource;
import com.aceitunaproject.server.installation.InstallationType;
import com.aceitunaproject.server.materialgoods.MaterialGood;
import com.aceitunaproject.server.science.ScientificAdvance;
import com.aceitunaproject.server.unit.UnitManager;
import com.aceitunaproject.shared.exceptions.gamerules.EnumIllegalAction;
import com.aceitunaproject.shared.exceptions.gamerules.IllegalActionException;
import com.aceitunaproject.shared.message.AceitunaSender;
import com.aceitunaproject.shared.message.Message;
import com.aceitunaproject.shared.message.MessageConstants;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;

/**
 * Player object
 * 
 * @author Maciej Miechowicz
 */
public class Player implements AceitunaSender, ManagedObject,
		Serializable {


	private static final long serialVersionUID = -865505294778439925L;

	private String name;

	private ManagedReference<ClientSession> sessionRef;

	Map<Long, ManagedReference<Unit>> units = new HashMap<Long, ManagedReference<Unit>>();

	
	private ManagedReference<Unit> getUnit(long unitID) throws IllegalActionException
	{
		ManagedReference<Unit> retUnit = units.get(unitID);
		if(retUnit == null)
		{
			throw new IllegalActionException(EnumIllegalAction.PERMISSION_UNIT_NOT_UNDER_CONTROL);
		}
		return retUnit;
	}
	
	public void moveUnit(long unitID, int destinationX, int destinationY)
			throws IllegalActionException {

		ManagedReference<Unit> unitToMove = getUnit(unitID);
		//checks permissions
		AceitunaContext.getPermissionManager().canMoveUnit(unitID,destinationX,destinationY,this);
		
		AceitunaContext.getUnitManager().moveUnit(
				unitToMove.getForUpdate(), destinationX, destinationY);

	}
	
	public void buildSettlement(long unitID,String name,int x, int y) throws IllegalActionException
	{
		AceitunaContext.getPermissionManager().canBuildSettlement(unitID,x,y,this);
		AceitunaContext.getUnitManager().buildSettlement(name, getUnit(unitID).getForUpdate(), x, y);
	}
	
	public void buildInstallation(long unitID,String installationType,String name,int x, int y) throws IllegalActionException
	{
		//TODO: this is a temporary fix making the building unit the source for all required materials
		List<GoodSource> sources = new LinkedList<GoodSource>();
		InstallationType type = AceitunaContext.getInstallationTypeManager().getAllInstallationTypes().get(installationType);
		for (MaterialGood good : type.getBuildingRequiredGoods().keySet()) {
		
			sources.add(new GoodSource(getUnit(unitID).getForUpdate(),good,type.getBuildingRequiredGoods().get(good)));
		}
		
		AceitunaContext.getPermissionManager().canBuildInstallation(unitID,installationType,x,y,this);
		AceitunaContext.getInstallationManager().buildInstallation(name,installationType ,getUnit(unitID).getForUpdate(), x, y,sources);
	}

	/**
	 * Creates a new player instance
	 * 
	 * @param name
	 *            player name
	 * @throws NullPointerException
	 *             if the name is null
	 */
	public Player(String name) {
		if (name == null)
			throw new NullPointerException("Null name");

		this.name = name;


		// Creates the Player's default unit.
		UnitManager unitManager = AceitunaContext.getUnitManager();
		Unit defUnit = unitManager.CreateUnit(name + " Default Unit",
				this, this);
		defUnit.setPersons(2);
		defUnit.addEquipment(AceitunaContext.getEquipmentManager().getAllEquipments().get("SettlementBuildingEquipment"));
		defUnit.addEquipment(AceitunaContext.getEquipmentManager().getAllEquipments().get("InstallationBuildingEquipment"));
		try {
			defUnit.addCargo(this, AceitunaContext.getMaterialGoodsManager().getAllMaterialGoods().get("InstallationBuildingMaterial"), Long.valueOf(2));
		} catch (IllegalActionException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		units.put(defUnit.getId(), AppContext.getDataManager().createReference(
				defUnit));

		// Puts the def unit in the 0,0 coordinates
		try {
			unitManager.moveUnit(defUnit, 0, 0);
		} catch (IllegalActionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Returns player name
	 * 
	 * @return player name
	 */
	public String getName() {
		return name;
	}

	
	/**
	 * Returns player session or <code>null</code> if session is not available
	 * 
	 * @return player session
	 */
	private ClientSession getSession() {
		return (sessionRef != null ? sessionRef.get() : null);
	}

	/**
	 * Sets player session
	 * 
	 * @param session
	 *            player session to set
	 */
	public void setSession(ClientSession session) {
		DataManager dataManager = AppContext.getDataManager();

		// mark self for update so the session reference can be updated
		dataManager.markForUpdate(this);
		sessionRef = session == null ?
	            null : dataManager.createReference(session);
	}

	

	/**
	 * Indicates if the player is connected
	 * 
	 * @return connection indicator
	 */
	public boolean isConnected() {
		ClientSession session = getSession();
		return (session != null && session.isConnected());
	}

	/**
	 * Sends a binary message to the client
	 * 
	 * @param message
	 *            binary message to send
	 * @throws NullPointerException
	 *             if the message is null
	 */
	public void sendBinaryMessage(ByteBuffer message) {
		if (message == null)
			throw new NullPointerException("Null message");

		ByteBuffer msg = ByteBuffer.allocate(1 + message.remaining());
		msg.put(MessageConstants.TYPE_BINARY);
		msg.put(message);
		msg.flip();
		sendMessage(msg);
	}

	/**
	 * Sends an object to the client
	 * 
	 * @param message
	 *            object to send
	 * @throws NullPointerException
	 *             if the message is null
	 */
	public void sendObjectMessage(Message message) {

		ServerConfiguration.getMessageSender(this).sendMessage(message);
	}

	/**
	 * Sends a message to the client
	 * 
	 * @param message
	 *            message to send
	 * @throws IllegalStateException
	 *             if the player is not connected
	 */
	public void sendMessage(ByteBuffer message) {
		ClientSession session = getSession();
		if (session == null)
			throw new IllegalStateException("Not connected");

		session.send(message);
	}

	

	/**
	 * @return the list of scientific advances known by this player
	 */
	public List<ScientificAdvance> getScientificAdvances() {
		// TODO Auto-generated method stub
		return new LinkedList<ScientificAdvance>();
	}

	public void removeUnit(Unit unit) {
		AppContext.getDataManager().markForUpdate(this);
		units.remove(unit.getId());
		
	}

	public void recruitUnit(long settlementId, String name, int persons) throws IllegalActionException {
		
		AceitunaContext.getPermissionManager().canRecruitUnit(settlementId,persons,this);
		Unit newUnit = AceitunaContext.getUnitManager().recruitUnit(settlementId,name,persons,this);
		AppContext.getDataManager().markForUpdate(this);
		units.put(newUnit.getId(), AppContext.getDataManager().createReference(newUnit));
		
	}

	public void attackUnit(long elementId, long enemyUnitId) throws IllegalActionException {
		ManagedReference<Unit> attackingUnit = getUnit(elementId);
			
		AceitunaContext.getCombatSystem().manageCombat(attackingUnit.getForUpdate(),enemyUnitId);

	}

}
